package com.siteseat.common.misc;

import java.io.File;
import java.io.IOException;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.PropertyConfigurator;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import com.siteseat.common.foundation.ObjectLogger;
import com.siteseat.common.security.PasswordFileHelper;
import com.siteseat.common.spring.ModularContext;


/**
 *
 */
public class Startup extends Shutdownable {

  public static final String SERVER_NAME_KEY = "server_name";
  public static final String SERVER_NAME_KEY_ARGUMENT = SERVER_NAME_KEY + "=";
  public static final String DEFAULT_SERVER_NMAE_VALUE = "Server";
  
  
  public static final String CONFIG_KEY = "spring";
  public static final String CONFIG_KEY_ARGUMENT = CONFIG_KEY + "=";
  public static final String DEFAULT_CONFIG_VALUE = "classpath:/server.xml";
  
  public static final String LOG_KEY = "log_file";
  public static final String LOG_KEY_ARGUMENT = LOG_KEY + "=";
  public static final String DEFAULT_LOG_VALUE = "classpath:/log4j.properties";
  
  public static final String LOG_DELAY_KEY = "log_delay";
  public static final String LOG_DELAY_KEY_ARGUMENT = LOG_DELAY_KEY + "=";
  public static final long DEFAULT_LOG_DELAY_VALUE = 3000;
  
  public static final String PASSWORD_FILE_KEY = "password_file";
  public static final String PASSWORD_FILE_KEY_ARGUMENT = PASSWORD_FILE_KEY + "=";
  
  private ModularContext context;
  
  private ObjectLogger log;
  
  private String configFile;
  
  private String logFile;
  
  private long logDelay;
  
  private String passwordFilePath;
  
  private String serverName;
  
  private String version;
  
  private String developmentDescription;
  
  public Startup() {
    setVersion( "version 1.0.0 Final (Tianan)" );
    setDevelopmentDescription( "License: LGPL; Author: Yi-ting Qiang (suntoech@sh163.net)" );
  }
  
  /**
   * Gets the log
   *
   * @return the log
   */
  public ObjectLogger getLog() {
    return log;
  }

  /**
   * Gets the context
   *
   * @return the context
   */
  public ModularContext getContext() {
    return context;
  }

  public String getServerName() {
    return serverName;
  }
  
  public String getVersion() {
    return this.version;
  }
  
  /**
   * Sets the version
   *
   * @param version the version to set
   */
  public void setVersion( String version ) {
    this.version = version;
  }

  public String getServerDescription() {
    return getServerName() + " " + getVersion();
  }
  
  public String getDevelopmentDescription() {
    return this.developmentDescription;
  }
  
  /**
   * Sets the developmentDescription
   *
   * @param developmentDescription the developmentDescription to set
   */
  public void setDevelopmentDescription( String developmentDescription ) {
    this.developmentDescription = developmentDescription;
  }

  /**
   * 
   *
   * @return
   */
  public String getSystemDescription() {
    StringBuilder t = new StringBuilder();
    
    Runtime rt = Runtime.getRuntime();
    t.append( "JVM Memory:" );
    t.append( "free: " + SizeUnit.MB.convertFrom( rt.freeMemory(), SizeUnit.BYTE ) ).append( "MB, " );
    t.append( "max: " + SizeUnit.MB.convertFrom( rt.maxMemory(), SizeUnit.BYTE ) ).append( "MB, " );
    t.append( "total: " + SizeUnit.MB.convertFrom( rt.totalMemory(), SizeUnit.BYTE ) ).append( "MB" );
    
    return t.toString();
  }
  
  /**
   * 
   *
   * @param exitStatus
   */
  public void printUsage() {    
    String mainClass = getClass().getName();
    String msg = "java -cp <...> ";
    msg += mainClass;
    msg += " " + CONFIG_KEY_ARGUMENT + DEFAULT_CONFIG_VALUE;
    msg += " " + LOG_KEY_ARGUMENT + DEFAULT_LOG_VALUE;
    msg += " " + PASSWORD_FILE_KEY_ARGUMENT + "<...>";
    
    System.out.println( msg );
  }
  
  /**
   * 
   *
   * @param args
   * @return
   */
  public int parseCommandLine( String[] args )  {    
    for( int i = 0; i < args.length; i++ ) {
      String arg = args[i];
      
      if( arg.startsWith( CONFIG_KEY_ARGUMENT ) ) {
        configFile = arg.substring( CONFIG_KEY_ARGUMENT.length() );
        if( StringUtils.isBlank(configFile) ) {
          return 1;
        }
      }
      else if( arg.startsWith( LOG_KEY_ARGUMENT ) ) {
        logFile = arg.substring( LOG_KEY_ARGUMENT.length() );
        if( StringUtils.isBlank(logFile) ) {
          return 2;
        }
      }
      else if( arg.startsWith( LOG_DELAY_KEY_ARGUMENT ) ) {
        String logDelayText = arg.substring( LOG_DELAY_KEY_ARGUMENT.length() );
        if( StringUtils.isBlank(logDelayText) ) {
          return 3;
        }
        try {
          logDelay = Long.parseLong( logDelayText );
        } catch( NumberFormatException nfe ) {
          nfe.printStackTrace( System.err );
          return 4;
        }
      }
      else if( arg.startsWith( PASSWORD_FILE_KEY_ARGUMENT ) ) {
        passwordFilePath = arg.substring( PASSWORD_FILE_KEY_ARGUMENT.length() );
        if( StringUtils.isBlank(passwordFilePath) ) {
          return 5;
        }
      }
      if( arg.startsWith( SERVER_NAME_KEY_ARGUMENT ) ) {
        serverName = arg.substring( SERVER_NAME_KEY_ARGUMENT.length() );
        if( StringUtils.isBlank(serverName) ) {
          return 6;
        }
      }
      else if( arg.equals( "help" ) ) {
        return -1;
      }
    }
    
    return 0;
  }
  
  /**
   * 
   *
   * @throws Exception
   */
  public int configureContext() {
    if( configFile == null ) {
      configFile = System.getProperty( CONFIG_KEY, DEFAULT_CONFIG_VALUE );
      if( StringUtils.isBlank(configFile) ) {
        return 7;
      }
    }
    
    this.context = new ModularContext();
    this.context.addConfigurationFile( configFile );
    
    return 0;
  }
  
  /**
   * @throws IOException 
   * 
   */
  public int configureLogger() throws IOException {
    if( logFile == null ) {
      logFile = System.getProperty( CONFIG_KEY, DEFAULT_LOG_VALUE );
      if( StringUtils.isBlank(logFile) ) {
        return 8;
      }
    }
    
    if( logDelay == 0 ) {
      logDelay = DEFAULT_LOG_DELAY_VALUE;
    }
    
    ResourceLoader rl = new DefaultResourceLoader();
    Resource r = rl.getResource( logFile );
    PropertyConfigurator.configureAndWatch( r.getURL().getFile(), logDelay );
    
    this.log = new ObjectLogger( Startup.class, getServerName() + "_bootstrap" );
    
    return 0;
  }
  
  /**
   * @throws IOException 
   * 
   */
  public int checkPassword() throws IOException {
    if( System.console() == null ) {
      return 0;
    }
    
    if( passwordFilePath == null ) {
      passwordFilePath = System.getProperty( PASSWORD_FILE_KEY );
      if( passwordFilePath == null ) {
        return 9;
      }
    }
    PasswordFileHelper ph = new PasswordFileHelper( passwordFilePath );
    if( ph.verify( true ) == false ) {
      System.out.println( "Wrong bootstrap password" );
      System.exit( 8 );
      return 10;
    }
    
    return 0;
  }
  
  /**
   * 
   *
   * @param args
   * @throws Exception
   */
  public void execute( String[] args ) throws Exception {
    int sc;
    
    sc = parseCommandLine( args );
    if( sc != 0 ) {
      printUsage();
      System.exit( sc );
    }
    
    sc = checkPassword();
    if( sc != 0 ) {
      printUsage();
      System.exit( sc );
    }
    
    sc = configureLogger();
    if( sc != 0 ) {
      printUsage();
      System.exit( sc );
    }    
    
    String home = determineHome();
    if( home == null ) {
      printUsage();
      System.exit( 11 );
      return;
    }
    
    File shutdownSignalFile = resolveShutdownSignalFile();
    if( shutdownSignalFile == null ) {
      printUsage();
      System.exit( 12 );
      return;
    }  
    
    long startingTime = System.nanoTime();
    
    log.info("\n********************************************************************************************************************************************");
    log.info( "Starting " + getServerDescription() );
    
    log.info( getDevelopmentDescription() );
    log.info( getSystemDescription() );
    
    sc = configureContext();
    if( sc != 0 ) {
      printUsage();
      System.exit( sc );
    }
    
    final ModularContext ctx = getContext();
    ctx.refresh();
        
    Runtime.getRuntime().addShutdownHook( new Thread() {
      @SuppressWarnings( "synthetic-access" )
      @Override
      public void run() {
        getLog().info( "Got shutdown request via console" );
        shutdown();
      }
    } );
    
    log.info( "GC-ing" );
    System.gc();
    
    log.info( getSystemDescription() );
    
    long startedTime = System.nanoTime();
    double timeCost = (startedTime - startingTime) / 1000000.0f;
    log.info( getServerDescription() + " is started in " + timeCost + " ms.\n********************************************************************************************************************************************");
    
    // Wait for shutdown

    if( shutdownSignalFile.exists() ) {
      shutdownSignalFile.delete();
    }
    while( true ) {
      Thread.sleep( getShutdownCheckInterval() );
      if( shutdownSignalFile.exists() ) {
        log.info( "Got shutdown request via " + shutdownSignalFile );
        shutdown();
        shutdownSignalFile.delete();
        break;
      }
    }
  }
  
  /**
   * 
   */
  private void shutdown() {
    log.info( "Stopping " + getServerDescription() );
    try {
      getContext().destroy();
    } catch( Exception e ) {
      log.error( "Error during shutdown", e );
    }
    log.info( getServerDescription() + " is stopped" );
  }
  
  /**
   *
   * @param args
   */
  public static void main( String[] args ) throws Exception {
    INSTANCE.execute( args );
  }

  public static final Startup INSTANCE = new Startup();
  
}
